package com.jackrain.nea.web.controller;

import com.alibaba.fastjson.JSONObject;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.*;
import com.jackrain.nea.config.PropertiesConf;
import com.jackrain.nea.util.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.time.FastDateFormat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;


@RestController
@Api(value = "UploadController", description = "文件上传")
public class UploadController {

    @Autowired
    private PropertiesConf pconf;

    public static OSSClient ossClient;
    public static String endpoint = "oss-cn-shanghai.aliyuncs.com";
    public static String bucketName = "raincloud";
    public static ConcurrentHashMap<String,JSONObject> uploadIdMap = new ConcurrentHashMap<>();
    public static final FastDateFormat dateFormatter = FastDateFormat.getInstance("yyMMddHHmmss");


    @PostConstruct
    public void afterInit() {
        if (ossClient == null) {
            String accessKeyId = pconf.getProperty("ons.accessKey");
            String accessKeySecret = pconf.getProperty("ons.secretKey");
            ossClient = new OSSClient("http://" + endpoint, accessKeyId, accessKeySecret);
        }

    }


    @RequestMapping(path="/p/c/upload",method= RequestMethod.POST)
    @ApiOperation(value="文件上传",httpMethod="POST")
    public JSONObject upload(@RequestParam(value = "path",required = true) String path,
                             @RequestParam(value = "file",required = true) MultipartFile file,
                             HttpServletRequest request, HttpSession session)throws Exception {
        ValueHolder vh = new ValueHolder();
        JSONObject result = new JSONObject();
        if (file == null || file.isEmpty()) {
            // 未选择文件
            vh.put("code", -1);
            vh.put("message", "未选择文件");
            return vh.toJSONObject();
        } else {
            // 文件原名称
            String fileName = file.getOriginalFilename();
            path = Tools.nvl(path,"");
            String key = path + fileName;
            try {

                if (ossClient.doesObjectExist(bucketName, key)) {
                    String fileExt = fileName.substring(fileName.lastIndexOf("."));
                    String origname = fileName.substring(0,fileName.lastIndexOf("."));
                    String timeStamp = dateFormatter.format(new Date());
                    key = path + origname + timeStamp + fileExt;
                }
            /*
             * Claim a upload id firstly
             */
                String uploadId = claimUploadId(key);
                System.out.println("Claiming a new upload id " + uploadId + "\n");

            /*
             * Calculate how many parts to be divided
             */
                final long partSize = 100 * 1024L;   // 5MB
                long fileLength = file.getSize();
                int partCount = (int) (fileLength / partSize);
                if (fileLength % partSize != 0) {
                    partCount++;
                }
                if (partCount > 10000) {
                    throw new RuntimeException("Total parts count should not exceed 10000");
                } else {
                    System.out.println("Total parts count " + partCount + "\n");
                }

            /*
             * Upload multiparts to your bucket
             */
                List<PartETag> partETags = Collections.synchronizedList(new ArrayList<PartETag>());
                ExecutorService executorService = Executors.newFixedThreadPool(5);
                System.out.println("Begin to upload multiparts to OSS from a file\n");
                for (int i = 0; i < partCount; i++) {
                    long startPos = i * partSize;
                    long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
                    executorService.execute(new UploadController.PartUploader(file.getInputStream(), startPos, curPartSize, i + 1, uploadId,key,partETags));
                }

                JSONObject jo = new JSONObject();
                jo.put("key",key);
                jo.put("partCount",partCount);
                uploadIdMap.put(uploadId,jo);

            /*
             * Waiting for all parts finished
             */
                executorService.shutdown();
                while (!executorService.isTerminated()) {
                    try {
                        executorService.awaitTermination(5, TimeUnit.SECONDS);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            /*
             * Verify whether all parts are finished
             */
                if (partETags.size() != partCount) {
                    throw new IllegalStateException("Upload multiparts fail due to some parts are not finished yet");
                } else {
                    System.out.println("Succeed to complete multiparts into an object named " + key + "\n");
                }

            /*
             * View all parts uploaded recently
             */
                listAllParts(uploadId,key);

            /*
             * Complete to upload multiparts
             */
                completeMultipartUpload(uploadId,key,partETags);

                result.put("uploadId",uploadId);
                result.put("url","http://" + bucketName + "." + endpoint + "/" + key);

            } catch (OSSException oe) {
                vh.put("code",-1);
                vh.put("message","Error Code:" + oe.getErrorCode()+
                        " Request ID:      " + oe.getRequestId()+
                        " Host ID:           " + oe.getHostId());
            } catch (ClientException ce) {
                vh.put("code",-1);
                vh.put("message","Error Code:" + ce.getMessage());
            }


            vh.put("code",0);
            vh.put("data",result);
            vh.put("message","Success");

            return vh.toJSONObject();
        }
    }

    private  String claimUploadId(String key) {
        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, key);
        InitiateMultipartUploadResult result = ossClient.initiateMultipartUpload(request);
        return result.getUploadId();
    }

    private  void completeMultipartUpload(String uploadId,String key,List<PartETag> partETags) {
        // Make part numbers in ascending order
        Collections.sort(partETags, new Comparator<PartETag>() {

            @Override
            public int compare(PartETag p1, PartETag p2) {
                return p1.getPartNumber() - p2.getPartNumber();
            }
        });

        System.out.println("Completing to upload multiparts\n");
        CompleteMultipartUploadRequest completeMultipartUploadRequest =
                new CompleteMultipartUploadRequest(bucketName, key, uploadId, partETags);
        ossClient.completeMultipartUpload(completeMultipartUploadRequest);
    }

     private void listAllParts(String uploadId,String key) {
        System.out.println("Listing all parts......");
        ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, key, uploadId);
        PartListing partListing = ossClient.listParts(listPartsRequest);

        int partCount = partListing.getParts().size();
        for (int i = 0; i < partCount; i++) {
            PartSummary partSummary = partListing.getParts().get(i);
            System.out.println("\tPart#" + partSummary.getPartNumber() + ", ETag=" + partSummary.getETag());
        }
        System.out.println();
    }

    private  class PartUploader implements Runnable {

        private InputStream instream;
        private long startPos;
        private long partSize;
        private int partNumber;
        private String uploadId;
        private String key;
        private List<PartETag> partETags;

        public PartUploader(InputStream instream, long startPos, long partSize, int partNumber, String uploadId,String key,List<PartETag> partETags) {
            this.instream = instream;
            this.startPos = startPos;
            this.partSize = partSize;
            this.partNumber = partNumber;
            this.uploadId = uploadId;
            this.key = key;
            this.partETags = partETags;
        }

        @Override
        public void run() {
            try {
                instream.skip(this.startPos);

                UploadPartRequest uploadPartRequest = new UploadPartRequest();
                uploadPartRequest.setBucketName(bucketName);
                uploadPartRequest.setKey(key);
                uploadPartRequest.setUploadId(this.uploadId);
                uploadPartRequest.setInputStream(instream);
                uploadPartRequest.setPartSize(this.partSize);
                uploadPartRequest.setPartNumber(this.partNumber);

                UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
                System.out.println("Part#" + this.partNumber + " done\n");
                synchronized (partETags) {
                    partETags.add(uploadPartResult.getPartETag());
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (instream != null) {
                    try {
                        instream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * 删除文件或者文件夹
     * @param file 文件夹名称或者（文件夹名称+文件名称）
     * @return
     * @throws Exception
     */
    @RequestMapping(path="/p/c/deleteObject",method= RequestMethod.POST)
    @ApiOperation(value="删除文件",httpMethod="POST")
    public  ValueHolder deleteObject(@RequestParam(value = "file",required = true) String file)throws Exception {
        ValueHolder vh = new ValueHolder();
        try {
            if (ossClient.doesObjectExist(bucketName, file)) {
                ossClient.deleteObject(bucketName, file);
                vh.put("code",0);
                vh.put("message","Success");
                return vh;
            }else{
                vh.put("code",-1);
                vh.put("message","NoSuchKey");
                return vh;
            }

        } catch (OSSException oe) {
            vh.put("code",-1);
            vh.put("message","Error Code:" + oe.getErrorCode()+
                    " Request ID:      " + oe.getRequestId()+
                    " Host ID:           " + oe.getHostId());
        } catch (ClientException ce) {
            vh.put("code",-1);
            vh.put("message","Error Code:" + ce.getMessage());
        }
        return vh;

    }
    /**
     * 删除文件或者文件夹
     * @param folder 文件夹名称或者（文件夹名称+文件名称）
     * @return
     * @throws Exception
     */
    @RequestMapping(path="/p/c/deleteFolder",method= RequestMethod.POST)
    @ApiOperation(value="删除文件夹",httpMethod="POST")
    public  JSONObject deleteFolder(@RequestParam(value = "folder",required = true) String folder)throws Exception {
        ValueHolder vh = new ValueHolder();
        try {

            ListObjectsRequest listObjectsRequest = new ListObjectsRequest(bucketName);
            listObjectsRequest.setPrefix(folder);

            ObjectListing listing = ossClient.listObjects(listObjectsRequest);

            for (OSSObjectSummary objectSummary : listing.getObjectSummaries()) {
                String key = objectSummary.getKey();
                if (ossClient.doesObjectExist(bucketName, key)) {
                    ossClient.deleteObject(bucketName, key);
                }
            }

            vh.put("code",0);
            vh.put("message","Success");

        } catch (OSSException oe) {
            vh.put("code",-1);
            vh.put("message","Error Code:" + oe.getErrorCode()+
                    " Request ID:      " + oe.getRequestId()+
                    " Host ID:           " + oe.getHostId());
        } catch (ClientException ce) {
            vh.put("code",-1);
            vh.put("message","Error Code:" + ce.getMessage());
        }
        return vh.toJSONObject();

    }


    /**
     * 页面上进度查询走这个方法
     * @return
     */
    @RequestMapping(path="/p/c/uploadProgress",method= RequestMethod.POST)
    @ApiOperation(value="文件上传进度查询",httpMethod="POST")
    public JSONObject uploadProgress(@RequestParam(value = "uploadId",required = true) String uploadId){
        ValueHolder vh = new ValueHolder();
        JSONObject jo = uploadIdMap.get(uploadId);
        int progress = 0;
        try {
            if(jo != null){
                //获得OSS上面的已经好了的分片和总分片相除获得进度
                ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, jo.getString("key"), uploadId);
                PartListing partListing;
                int partCount = jo.getIntValue("partCount");
                partListing = ossClient.listParts(listPartsRequest);
                Integer nowPartCount = partListing.getParts().size();
                progress = nowPartCount/partCount * 100;

            }else{
                progress = 1;
            }

        }catch (Exception e){
            if(jo != null) {
                if (ossClient.doesObjectExist(bucketName, jo.getString("key"))) {
                    progress = 100;
                }else{
                    vh.put("code",-1);
                    vh.put("message","Fail");
                }
            }
        }
        if(vh.get("code") == null){
            if(progress == 100){
                uploadIdMap.remove(uploadId);
            }
            vh.put("code",0);
            vh.put("data",progress);
            return vh.toJSONObject();
        }else{
            return vh.toJSONObject();
        }

    }

}

